package log

import (
	"bufio"
	"encoding/json"
	"io"
	"os"
	"sync"
	"time"
)

const (
	defaultBufferSize  = 1024
	maxJSONDecodeRetry = 20000
)

type IOLogger interface {
	io.WriteCloser
	Start()
	ReadLogs(ReadConfig) *Watcher
}

type logger struct {
	logPath   string
	logFile   *os.File
	mu        sync.Mutex
	bufWriter *io.PipeWriter
	bufReader *bufio.Reader
}

func NewIOLogger(path string) (IOLogger, error) {
	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return nil, err
	}

	reader, writer := io.Pipe()
	logger := &logger{
		logPath:   path,
		logFile:   file,
		bufWriter: writer,
		bufReader: bufio.NewReader(reader), // default size = 4k
	}
	return logger, nil
}

func OpenIOLogger(path string) (IOLogger, error) {
	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return nil, err
	}
	reader, writer := io.Pipe()
	logger := &logger{
		logPath:   path,
		logFile:   file,
		bufWriter: writer,
		bufReader: bufio.NewReader(reader), // default size = 4k
	}
	return logger, nil
}

func (log *logger) Write(data []byte) (int, error) {
	log.mu.Lock()
	n, err := log.bufWriter.Write(data)
	log.mu.Unlock()
	return n, err
}

func (log *logger) Close() error {
	return log.bufWriter.Close()
}

func (log *logger) ReadLogs(config ReadConfig) *Watcher {
	logWatcher := NewWatcher()
	go func() {
		log.mu.Lock()
		defer close(logWatcher.Msg)
		file, err := os.Open(log.logPath)
		if err != nil {
			logWatcher.Err <- err
			log.mu.Unlock()
			return
		}
		if config.Tail != 0 {
			tailFile(file, logWatcher, config.Tail, config.Since)
		}
		if !config.Follow {
			if err := file.Close(); err != nil {
				logWatcher.Err <- err
			}
			log.mu.Unlock()
			return
		}
		// 先让 tailFile 全部读完 再让 follow 读
		// 首先将 file seek 指向最后
		if config.Tail >= 0 {
			file.Seek(0, io.SeekEnd)
		}
		// 解除锁 开始写
		log.mu.Unlock()

		followLogs(file, logWatcher, config.Since)
	}()
	return logWatcher
}

func (log *logger) Start() {
	go log.writer()
}

func (log *logger) writer() {
	entry := Entry{}
	for {
		line, err := log.bufReader.ReadSlice('\n')
		entry.LogTime = time.Now().UTC().UnixNano()
		entry.Line = string(line)
		lineJson, _ := json.Marshal(entry)
		if _, err := log.logFile.Write(append(lineJson, '\n')); err != nil {
			break
		}
		if err == io.EOF {
			break
		}
	}
	_ = log.logFile.Close()
}

type Entry struct {
	LogTime int64  `json:"t"`
	Line    string `json:"d"`
}
